/*
 * Look for a open wireless networks.
 * Based in code from wireless tools [ iwlist ]
 *
 * Ignacio Lopez
 * nacholopez at gmail dot com
 *
 */

#include <iwlib.h>		/* Header */
#include <sys/time.h>
#include <net/ethernet.h>      /* ether_addr */

#define debug() 1

/****************************** TYPES ******************************/

/*
 * Scan state and meta-information, used to decode events...
 */
typedef struct iwscan_state
{
  /* State */
  int			ap_num;		/* Access Point number 1->N */
  int			val_index;	/* Value in table 0->(N-1) */
} iwscan_state;

/*------------------------------------------------------------------*/


/*
 * Print the iwconfig command properly based in the suitable networks found 
 */
void __not_work_print_cmd_open_networks(char* ifname, wireless_scan* wscan, int index)
{
	int i;
	wireless_scan* ws=NULL;

	ws=wscan;
	for (i=0;i<=index;i++){
		/* Look if the network is suitable 
		   has_ap_addr == 1
		   b.has_mode == 1
		   b.mode == 3 (IW_MODE_MASTER) wireless.h
		   b.has_essid == 1
		   b.has_key == 0
		*/
		printf("has_ap_addr: %d, b.has_mode: %d, b.mode: %d, b.has_essid: %d, b.has_key: %d\n", \
		       ws->has_ap_addr, ws->b.has_mode, ws->b.mode, ws->b.has_essid, ws->b.has_key);
		       
		if ( ws->has_ap_addr == 1 && \
		     ws->b.has_mode == 1 && \
		     ws->b.mode == IW_MODE_MASTER && \
		     ws->b.has_essid == 1 && \
		     ws->b.has_key == 0 ){
			printf("iwconfig %s mode Managed essid \"%s\" enc off rate 1M\n",ifname,ws->b.essid);
		}
		else {
			printf("AP: %s no suitable.\n",ws->b.essid);
		}
		ws=ws->next;
	}
}


int connect_to(const char* ifname, const wireless_scan* wscan)
{
	char cmd[512]; /* todo, check the legth */

	if (wscan == NULL) /* No networks to connect */ 
		return -1;

	if (debug()) printf("Connecting to the network \"%s\"\n",wscan->b.essid);
	sprintf(cmd,"iwconfig %s mode Managed enc off essid \"%s\"",ifname,wscan->b.essid);
	if (debug()) printf("Launching command : %s\n",cmd);
	system(cmd);
	/* Check if system returns ok */
	return 0;
}

/*------------------------------------------------------------------*/
/*
 * Free the wscan list 
 */
void free_wscan(wireless_scan* wscan, int *index)
{
	wireless_scan* wstmp;
	int i;
	int count;
	
	if ( *index < 0 || wscan == NULL ) /* It doesn't have to free memory */ 
		return;

	
	for (i = 0; i< *index; i++){
		wstmp = wscan; /* it points to the first(0) element */
		for (count = 0; count < *index - i; count ++) /* it points the last element */
			wstmp=wstmp->next;
		//if (wstmp->next!=NULL) printf("Warning: something went wrong. Check the code\n");
		//printf("Eliminando elemento : %d\n",i);
		//wstmp->next = NULL;
		free(wstmp);
	}
	
	/* all has been free. */
	free(wscan);
	wscan = NULL;
	*index = -1;
	return;	
}


/*------------------------------------------------------------------*/
/*
 * Print one element from the scanning results.
 * Return < 0 if an error happens.
 */
int __scan_token(struct stream_descr *	stream,	/* Stream of events */
		     struct iw_event *		event,	/* Extracted token */
		     struct iwscan_state *	state,
		     struct iw_range *	iw_range,	/* Range info */
		     int		has_range,
		     wireless_scan *wscan, /* Pointer to the first element found */
		     int *wscan_length /* Number of networks found */
)
{
  char		buffer[128];	/* Temporary buffer */
  int i;
  wireless_scan *ws = NULL;
  wireless_scan *wstmp = NULL;

  ws = wscan;
  for (i=0; i < *wscan_length; i++) /* It points the last element */
	ws=ws->next;

  /* Now, let's decode the event */
  switch(event->cmd)
    {
    case SIOCGIWAP:
      /* We are dealing with the first element */
      if ( *wscan_length < 0 ){
	      printf("---------------------------\n");
	      if (wscan == NULL)
		      return -1;
	      wscan->next=NULL;
	      *wscan_length = 0 ;
	      ws = wscan;
      }
      else{
	      printf("---------------------------\n");
	      wstmp = (wireless_scan *) malloc (sizeof(wireless_scan));
	      if (debug()) printf("1 objecto wireless scan creado \n");
	      
	      if (wstmp == NULL)
		      return -1;
	      wstmp->next=NULL;
	      ws->next = wstmp;
	      *wscan_length=*wscan_length + 1;
	      ws = ws->next;
      }
      if (debug()) printf("Index : %d ", *wscan_length);
      ws->has_ap_addr=0;
      ws->b.has_mode=0;
      ws->b.has_essid=0;
      ws->has_stats=0;
      ws->b.has_key=1; /* We suppose the ap is closed */

      memcpy(&ws->ap_addr,&event->u.ap_addr,sizeof(sockaddr));
      ws->has_ap_addr=1;
      if (debug()) printf("MAC: %s ",iw_saether_ntop(&ws->ap_addr, buffer));
      state->ap_num++;
      break;
       
      /* NWID */
    case SIOCGIWNWID:
      if(event->u.nwid.disabled)
	if (debug()) printf("                    NWID:off/any\n");
      else
	if (debug()) printf("                    NWID:%X\n", event->u.nwid.value); 
      break;
      
      /* CHANNEL */
    case SIOCGIWFREQ:
      {
	double		freq;			/* Frequency/channel */
	int		channel = -1;		/* Converted to channel */
	freq = iw_freq2float(&(event->u.freq));
	/* Convert to channel if possible */
	if(has_range)
	  channel = iw_freq_to_channel(freq, iw_range);
	iw_print_freq(buffer, sizeof(buffer),
		      freq, channel, event->u.freq.flags);
	//printf("                    %s\n", buffer);
      }
      break;
      /* MODE */
    case SIOCGIWMODE:
	    /*printf("                    Mode:%s\n",
	      iw_operation_mode[event->u.mode]); */ // 3 is Master mode (iwlib.c)
	    memcpy(&ws->b.mode,&event->u.mode,sizeof(int));
	    ws->b.has_mode=1;
	    if (debug()) printf ("Modo :%s ",iw_operation_mode[ws->b.mode]);
      break;
      /* PROTOCOL NAME */
    case SIOCGIWNAME:
	    //printf("                    Protocol:%-1.16s\n", event->u.name);
      break;
      /* ESSID */
    case SIOCGIWESSID:
      {
	char essid[IW_ESSID_MAX_SIZE+1];
	memset(essid, '\0', sizeof(essid));
	if((event->u.essid.pointer) && (event->u.essid.length))
	  memcpy(essid, event->u.essid.pointer, event->u.essid.length);
	if(event->u.essid.flags)
	  {
	    /* Does it have an ESSID index ? */
	    /* I don't know what's this -- Nacho */
		  /* 
	    if((event->u.essid.flags & IW_ENCODE_INDEX) > 1)
	      printf("                    ESSID:\"%s\" [%d]\n", essid,
		     (event->u.essid.flags & IW_ENCODE_INDEX));
	    else
	    printf("                    ESSID:\"%s\"\n", essid);
		  */
	    memset(ws->b.essid,'\0',sizeof(essid));
	    memcpy(ws->b.essid,essid,event->u.essid.length);
	    ws->b.has_essid=1;
	    if (debug()) printf("Essid : \"%s\" ",ws->b.essid);
	  }
	else{
	  printf("                    ESSID:off/any/hidden\n");
	  ws->b.essid_on=0;
	}
      }
      break;
      /* ENCONDE */
    case SIOCGIWENCODE:
      {
	unsigned char	key[IW_ENCODING_TOKEN_MAX];

	if(event->u.data.pointer)
	  memcpy(key, event->u.data.pointer, event->u.data.length);
	else
	  event->u.data.flags |= IW_ENCODE_NOKEY;
	
	//printf("                    Encryption key:");
	
	if(event->u.data.flags & IW_ENCODE_DISABLED){
	  if (debug()) printf("No encryption ");
	  ws->b.has_key=0;
	}
	else
	  {
	    if (debug()) printf("Encryption enabled ");
	    /* Display the key */
	    iw_print_key(buffer, sizeof(buffer), key, event->u.data.length,
			 event->u.data.flags);
	    //printf("%s", buffer);

	    /* Other info... */
	    /*
	    if((event->u.data.flags & IW_ENCODE_INDEX) > 1)
	      printf(" [%d]", event->u.data.flags & IW_ENCODE_INDEX);
	    if(event->u.data.flags & IW_ENCODE_RESTRICTED)
	      printf("   Security mode:restricted");
	    if(event->u.data.flags & IW_ENCODE_OPEN)
	      printf("   Security mode:open");
	    printf("\n");
	    */
	  }
      }
      break;
      /* RATE */
    case SIOCGIWRATE:
	    /*
      if(state->val_index == 0)
	printf("                    Bit Rates:");
      else
	if((state->val_index % 5) == 0)
	  printf("\n                              ");
	else
	  printf("; ");
      iw_print_bitrate(buffer, sizeof(buffer), event->u.bitrate.value);
      printf("%s", buffer);
	    */
      /* Check for termination */
      /*
	    if(stream->value == NULL)
	{
	  printf("\n");
	  state->val_index = 0;
	}
      else
	state->val_index++;
      */
      break;
      /* EVQUAL */
    case IWEVQUAL:
      {
	      //iw_print_stats(buffer, sizeof(buffer),
	      //       &event->u.qual, iw_range, has_range);
	      //printf("                    %s\n", buffer);
	      memcpy(&ws->stats.qual,&event->u.qual,sizeof(struct iw_quality));
	      ws->has_stats=1;
	      if (debug()) printf("Link quality: %d.",ws->stats.qual.qual);
	break;
      }
    case IWEVGENIE:
      /* Informations Elements are complex, let's do only some of them */
      //iw_print_gen_ie(event->u.data.pointer, event->u.data.length);
      break;
    case IWEVCUSTOM:
      {
	char custom[IW_CUSTOM_MAX+1];
	if((event->u.data.pointer) && (event->u.data.length))
	  memcpy(custom, event->u.data.pointer, event->u.data.length);
	custom[event->u.data.length] = '\0';
	//printf("                    Extra:%s\n", custom);
      }
      break;
    default:
      printf("                    (Unknown Wireless Token 0x%04X)\n",
	     event->cmd);
   }	/* switch(event->cmd) */

  return 0;
}

/*------------------------------------------------------------------*/
/*
 * Perform a scanning on one device
 */
int __scan(const int skfd, const char *ifname, wireless_scan *wscan, int *wscan_index)
{
  
  struct iwreq		wrq;
  unsigned char *	buffer = NULL;		/* Results */
  int			buflen = IW_SCAN_MAX_DATA; /* Min for compat WE<17 */
  struct iw_range	range;
  int			has_range;
  struct timeval	tv;				/* Select timeout */
  int			timeout = 15000000;		/* 15s */


  /* Get range stuff */
  has_range = (iw_get_range_info(skfd, ifname, &range) >= 0);
  /* Check if the interface could support scanning. */
  if((!has_range) || (range.we_version_compiled < 14))
  {
      fprintf(stderr, "%-8.16s  Interface doesn't support scanning.\n\n", ifname);
      return(-1);
  }

  /* Init timeout value -> 250ms*/
  tv.tv_sec = 0;
  tv.tv_usec = 250000;

  /*
   * Here we should look at the command line args and set the IW_SCAN_ flags
   * properly
   */
  wrq.u.data.pointer = NULL;		/* Later */
  wrq.u.data.flags = 0;
  wrq.u.data.length = 0;

  /* Initiate Scanning */
  if(iw_set_ext(skfd, ifname, SIOCSIWSCAN, &wrq) < 0)
    {
      if(errno != EPERM)
	{
	  fprintf(stderr, "%-8.16s  Interface doesn't support scanning : %s\n\n", ifname, strerror(errno));
	  return(-1);
	}
      /* If we don't have the permission to initiate the scan, we may
       * still have permission to read left-over results.
       * But, don't wait !!! */
      tv.tv_usec = 0;
      fprintf(stderr, "Unknown error\n");
    }
  timeout -= tv.tv_usec;

  /* Forever */
  while(1)
    {
      fd_set		rfds;		/* File descriptors for select */
      int		last_fd;	/* Last fd */
      int		ret;

      /* Guess what ? We must re-generate rfds each time */
      FD_ZERO(&rfds);
      last_fd = -1;

      /* In here, add the rtnetlink fd in the list */

      /* Wait until something happens */
      ret = select(last_fd + 1, &rfds, NULL, NULL, &tv);

      /* Check if there was an error */
      if(ret < 0)
	{
	  if(errno == EAGAIN || errno == EINTR)
	    continue;
	  fprintf(stderr, "Unhandled signal - exiting...\n");
	  return(-1);
	}

      /* Check if there was a timeout */
      if(ret == 0)
	{
	  unsigned char *	newbuf;

	realloc:
	  /* (Re)allocate the buffer - realloc(NULL, len) == malloc(len) */
	  newbuf = realloc(buffer, buflen);
	  if(newbuf == NULL)
	    {
	      if(buffer)
		free(buffer);
	      fprintf(stderr, "%s: Allocation failed\n", __FUNCTION__);
	      return(-1);
	    }
	  buffer = newbuf;

	  /* Try to read the results */
	  wrq.u.data.pointer = buffer;
	  wrq.u.data.flags = 0;
	  wrq.u.data.length = buflen;
	  if(iw_get_ext(skfd, ifname, SIOCGIWSCAN, &wrq) < 0)
	    {
	      /* Check if buffer was too small (WE-17 only) */
	      if((errno == E2BIG) && (range.we_version_compiled > 16))
		{
		  /* Some driver may return very large scan results, either
		   * because there are many cells, or because they have many
		   * large elements in cells (like IWEVCUSTOM). Most will
		   * only need the regular sized buffer. We now use a dynamic
		   * allocation of the buffer to satisfy everybody. Of course,
		   * as we don't know in advance the size of the array, we try
		   * various increasing sizes. Jean II */

		  /* Check if the driver gave us any hints. */
		  if(wrq.u.data.length > buflen)
		    buflen = wrq.u.data.length;
		  else
		    buflen *= 2;

		  /* Try again */
		  goto realloc;
		}

	      /* Check if results not available yet */
	      if(errno == EAGAIN)
		{
		  /* Restart timer for only 100ms*/
		  tv.tv_sec = 0;
		  tv.tv_usec = 100000;
		  timeout -= tv.tv_usec;
		  if(timeout > 0)
		    continue;	/* Try again later */
		}

	      /* Bad error */
	      free(buffer);
	      fprintf(stderr, "%-8.16s  Failed to read scan data : %s\n\n",
		      ifname, strerror(errno));
	      return(-2);
	    }
	  else
	    /* We have the results, go to process them */
	    break;
	}

      /* In here, check if event and event type
       * if scan event, read results. All errors bad & no reset timeout */
    }

  if(wrq.u.data.length)
    {
      struct iw_event		iwe;
      struct stream_descr	stream;
      struct iwscan_state	state = { .ap_num = 1, .val_index = 0 };
      int			ret;
      
      if (debug()) printf("%-8.16s  Scan completed :\n", ifname);
      iw_init_event_stream(&stream, (char *) buffer, wrq.u.data.length);
      do
	{
	  /* Extract an event and print it */
	  ret = iw_extract_event_stream(&stream, &iwe,
					range.we_version_compiled);
	  if(ret > 0) {
		  if (__scan_token(&stream, &iwe, &state,
				   &range, has_range, wscan, wscan_index)<0)
			  return -1;
	  }
	}
      while(ret > 0);
      printf("\n");
    }
  else
    if (debug()) printf("%-8.16s  No scan results\n", ifname);

  free(buffer);
  
  return(0);
}

/* it performs an scan an fills wscan and wscan_counter */
/* return < 0 if an error happens */
/* wscan_index == -1 if there are not wireless networks */
int scan(const char *interface, wireless_scan *wscan, int *wscan_index)
{
	*wscan_index = -1;
	int skfd;
	int result;
	
	/* Create a channel to the NET kernel. */
	if((skfd = iw_sockets_open()) < 0){
		perror("socket");
		return -1;
	}

	/* do the scanning */
	result = __scan(skfd, interface, wscan, wscan_index);
	
        /* Close the socket. */
	iw_sockets_close(skfd);

	return result;
}

/* return the index of the list which is the best network
   to connect. < 0 is there are not suitable networks */
int best_network(const wireless_scan *wscan, const int wscan_index)
{
	int best_link = -1;

	if (wscan_index < 0 || wscan == NULL)
		return -1;

	best_link = __best_link(wscan, wscan_index);

	return best_link;
}

/* return <>0 if the network is valid to connect */
inline int __net_is_valid(const wireless_scan *ws_temp)
{
	if ( ws_temp->has_ap_addr == 1 &&	     \
		     ws_temp->b.has_mode == 1 &&	     \
		     ws_temp->b.mode == IW_MODE_MASTER && \
		     ws_temp->b.has_essid == 1 &&	     \
		     ws_temp->b.has_key == 0 ){ /* it's perfect!! */
		return 1;
	}
	return 0;
		
}


/* return the index of the list which has the best quality link */
int __best_link(wireless_scan *wscan, const int wscan_index)
{
	int i;
	int result = -1;
	__u8 bestlink=0;
	wireless_scan* wstmp;
	
	wstmp = (wireless_scan *) wscan;
	for (i=0; i<=wscan_index; i++){
		if (__net_is_valid(wstmp)) {
			if (wstmp->stats.qual.qual >= bestlink) {
				bestlink = wstmp->stats.qual.qual;
				result = i;
			}
		}
		wstmp = wstmp->next;
	}

	return result;
}




